﻿
using System;
using System.IO;
using System.Windows.Media.Imaging;
using ImageConverter.Data;
using ImageConverter.Model.Enums;
using ImageConverter.Pooling;
using ImageConverter.Logging;
using ImageConverter.Configuration;
namespace ImageConverter.Utils.Bitmap
{
    public class BitmapImageEncoder
    {
        public IAspectsTable AspectsTable { get; private set; }

        public int MaxLength { get; set; }

        public BitmapImageEncoder(int maxLength)
        {
            if (maxLength <= 0)
                throw new ArgumentException("Illegal base length for bitmap encoder");

            AspectsTable = new AspectsTable();
            MaxLength = maxLength;
        }

        public BitmapImageEncoderResult Recode(EntryData data, string outputDir)
        {
            BitmapImageEncoderResult encdoderResult = new BitmapImageEncoderResult()
            {
                Result = ItemStatusEnum.Unknown
            };

            using (var stream = new MemoryStream(data.ByteData))
            {
                try
                {

                    var srcImage = GetSourceImage(stream);

                    int srcWidth = srcImage.PixelWidth;
                    int srcHeight = srcImage.PixelHeight;

                    encdoderResult.Source = DimensionsPool.Create(srcWidth, srcHeight);

                    //Update aspects table if needed
                    var resizedDims = AspectsTable.GetRecodeDimensions(srcWidth, srcHeight);
                    if (resizedDims == Dimensions.Empty) // new aspect, doesn't exist in AspectsTable
                    {
                        double imageAspect = (double)srcWidth / (double)srcHeight;

                        double koeff = -1d;

                        if (srcWidth < MaxLength && srcHeight < MaxLength) // too small image if compared to maxLength
                        {
                            AddNewAspectData((double)srcWidth / (double)srcHeight, srcWidth, srcHeight);
                            AddNewAspectData((double)srcHeight / (double)srcWidth, srcHeight, srcWidth);
                        }
                        else
                        {
                            if (imageAspect > 1) // horizontal image
                            {
                                koeff = (double)srcWidth / MaxLength;

                                int newWidth = (int)MaxLength;
                                int newHeight = (int)((double)srcHeight / koeff);
                                double newAspectRatio = (double)newWidth / (double)newHeight;

                                AddNewAspectData(newAspectRatio, newWidth, newHeight);

                                double newVerticalAspectRation = (double)newHeight / (double)newWidth;

                                AddNewAspectData(newVerticalAspectRation, newHeight, newWidth);
                            }
                            else
                            {
                                koeff = (double)srcHeight / MaxLength;

                                int newHeight = (int)MaxLength;
                                int newWidth = (int)((double)srcWidth / koeff);
                                double newAspectRatio = (double)newWidth / (double)newHeight;

                                AddNewAspectData(newAspectRatio, newWidth, newHeight);

                                double newVerticalAspectRation = (double)newHeight / (double)newWidth;

                                AddNewAspectData(newVerticalAspectRation, newHeight, newWidth);
                            }
                        }

                        //Resize image

                        long encodedSize = -1;

                        if (AspectsTable.IsSmallImage(srcWidth, srcHeight))
                        {
                            encodedSize = SaveImage(srcImage, Path.Combine(outputDir, data.FileName));

                            encdoderResult.Target = encdoderResult.Source;
                            encdoderResult.Result = ItemStatusEnum.Success;
                        }
                        else
                        {
                            Dimensions recodeDimensions = AspectsTable.GetRecodeDimensions(srcImage.PixelWidth, srcImage.PixelHeight);
                            if (recodeDimensions != Dimensions.Empty)
                            {
                                stream.Seek(0, SeekOrigin.Begin);
                                var resizedImage = ResizeImage(stream, recodeDimensions);
                                encodedSize = SaveImage(resizedImage, Path.Combine(outputDir, data.FileName));

                                encdoderResult.Target = recodeDimensions;
                                encdoderResult.Result = ItemStatusEnum.Success;
                            }
                            else // unknown aspect ratio
                            {
                                Logger.GetLog().Debug(
                                    String.Format("Unknown aspect ratio {0} {1}x{2}@{3}",
                                    Path.Combine(outputDir, data.FileName),
                                    (int)srcImage.Width, (int)srcImage.Height, (double)srcImage.Width / (double)srcImage.Height));

                                //TODO calculate new dimensions

                                encodedSize = SaveImage(srcImage, Path.Combine(outputDir, data.FileName));

                                encdoderResult.Target = encdoderResult.Source;
                                encdoderResult.Result = ItemStatusEnum.Warning;
                            }
                        }

                        encdoderResult.EncodedSize = encodedSize;
                    }
                }
                catch
                {
                    encdoderResult.Result = ItemStatusEnum.Error;
                }
            }

            return encdoderResult;
        }

        private void AddNewAspectData(double aspect, int width, int height)
        {
            AspectsTable.AddAspect(aspect, width, height);
        }

        private BitmapImage GetSourceImage(Stream stream)
        {
            BitmapImage result = null;

            try
            {
                result = new BitmapImage();
                result.CacheOption = BitmapCacheOption.OnLoad;
                result.BeginInit();
                result.StreamSource = stream;
                result.EndInit();
            }
            catch
            {
                result = null;
            }

            return result;
        }

        private BitmapImage ResizeImage(Stream stream, Dimensions newSize)
        {
            BitmapImage result = new BitmapImage();
            //result.CacheOption = BitmapCacheOption.OnLoad;
            
            result.BeginInit();

            result.StreamSource = stream;
            result.DecodePixelWidth = (int)newSize.Width;
            result.DecodePixelHeight = (int)newSize.Height;

            result.EndInit();

            return result;
        }

        /// <summary>
        /// Saves image into file using JpegBitmapEncoder and returns compressed image size
        /// </summary>
        /// <param name="img"></param>
        /// <param name="fileName"></param>
        /// <returns>Compressed image size</returns>
        private long SaveImage(BitmapImage img, string fileName)
        {
            long result = -1;

            JpegBitmapEncoder encoder = new JpegBitmapEncoder();
            encoder.QualityLevel = 80;
            encoder.Frames.Add(BitmapFrame.Create(img));

            using (FileStream fs = File.OpenWrite(fileName))
            {
                encoder.Save(fs);
                result = fs.Length;
            }

            return result;
        }

        private double GetImageAspectRatio(BitmapImage img)
        {
            return (double)img.PixelWidth / (double)img.PixelHeight;
        }
    }
}
